﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Shapes;
using System.Windows.Controls;
using Pavilion.DesignerTool.ViewTools;


namespace Pavilion.DesignerTool
{
    public class Connection : FrameworkElement
    {
        public static readonly DependencyProperty StartProperty = DependencyProperty.Register( "Start", typeof(FrameworkElement), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnStartPropertyChanged)));
        public static readonly DependencyProperty EndProperty = DependencyProperty.Register("End", typeof(FrameworkElement), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnEndPropertyChanged)));
        public static readonly DependencyProperty LineColorProperty = DependencyProperty.Register("LineColor", typeof(Brush), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnLineColorChanged)));

        public FrameworkElement Start { get { return (FrameworkElement)base.GetValue(StartProperty); } set { base.SetValue(StartProperty, value); InvalidateVisual(); } }
        public FrameworkElement End { get { return (FrameworkElement)base.GetValue(EndProperty); } set { base.SetValue(EndProperty, value); } }
        public Brush LineColor { get { return (Brush)base.GetValue(LineColorProperty); } set { base.SetValue(LineColorProperty, value); InvalidateVisual(); } }
       
        //startPoint and endPoint are given values in the getterto ensure that the points are assigned appropriate values when it is time to render.
        private Point? StartPoint { get { return startPoint ?? (startPoint = GetPointOnCenter(Start)); } set { startPoint = value; } }
        private Point? EndPoint { get { return endPoint ?? (endPoint = GetPointOnCenter(End)); } set { endPoint = value; } }

        private LineGeometry Path { get { return path; } set { path = value; base.InvalidateVisual(); } }//forces redraw
        private LineGeometry path;
        private Point? endPoint;
        private Point? startPoint;
        
        public Connection()
        {
            base.Loaded += (sender, e) => Path = CalculateNewPathGeometry();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            DrawConnection(drawingContext);
        }

        private LineGeometry CalculateNewPathGeometry()
        {
            if (Start == null || End == null)
                return null;

            AllignStartAndEndPoints();

            return new LineGeometry((Point)StartPoint, (Point)EndPoint);
        }

        private void AllignStartAndEndPoints()
        {
            var startPoint = GetPointOnCenter(Start);
            var endPoint = GetPointOnCenter(End);
            var startVector = new Microsoft.Xna.Framework.Vector2((float)startPoint.X, (float)startPoint.Y);
            var endVector = new Microsoft.Xna.Framework.Vector2((float)endPoint.X, (float)endPoint.Y);
            LineVector lineVector = new LineVector(startVector, endVector - startVector);

            StartPoint = DetermineIntersectingPoint(lineVector, Start);
            EndPoint = DetermineIntersectingPoint(lineVector, End);
        }

        private Point DetermineIntersectingPoint(LineVector lineVector, FrameworkElement box)
        {
            var canvas = (Canvas)PavilionVisualTreeHelper.GetAncestor(box, typeof(Canvas));

            Point point = box.TransformToAncestor(canvas).Transform(new Point(0, 0));
            var boxRectangle = new Microsoft.Xna.Framework.Rectangle((int)point.X, (int)point.Y, (int)box.ActualWidth, (int)box.ActualHeight);

            Microsoft.Xna.Framework.Vector2 intersectingPoint = LineVector.ObtainIntersactionPoint(lineVector, boxRectangle);

            return new Point((double)intersectingPoint.X, (double)intersectingPoint.Y);
        }

        private void DrawConnection(DrawingContext drawingContext)
        {
            var transParentBrush = new SolidColorBrush(Colors.Transparent);
            var pen = new Pen(LineColor, 1);
            var transparentPen = new Pen(transParentBrush, 6);
            drawingContext.DrawGeometry(LineColor, pen, LineOfArrow(90));
            drawingContext.DrawGeometry(LineColor, pen, LineOfArrow(180));
            drawingContext.DrawGeometry(LineColor, pen, Path);
            drawingContext.DrawGeometry(transParentBrush, transparentPen, Path);
        }

        private LineGeometry LineOfArrow(double theta)
        {
            LineGeometry line = new LineGeometry(new Point(0,0), new Point(10, 10));
            line.Transform = CreateAngleTransformation(theta);

            return line;
        }

        private TransformGroup CreateAngleTransformation(double theta)
        {
            var transformGroup = new TransformGroup();
            transformGroup.Children.Add(new RotateTransform(theta - ObtainAngleOfLine()));
            transformGroup.Children.Add(new TranslateTransform(((Point)EndPoint).X, ((Point)EndPoint).Y));
            return transformGroup;
        }

        private double ObtainAngleOfLine()
        {
            double xDisplacement = ((Point)EndPoint).X - ((Point)StartPoint).X;
            double yDisplacement = -((Point)EndPoint).Y + ((Point)StartPoint).Y;
            return RadianToDegree(Math.Atan2(yDisplacement, xDisplacement));
        }

        private double RadianToDegree(double angle)
        {
            return angle * 180 / 3.14;
        }

        public static Point GetPointOnCenter(FrameworkElement frameworkElement)
        {
            //for now we'll use the top left of the fwe. 

            //get a reference to the custom canvas. This dependency needs to be injected. 
            if (frameworkElement == null)
                return new Point(0, 0);
            else
            {
                var canvas = (Canvas)PavilionVisualTreeHelper.GetAncestor(frameworkElement, typeof(Canvas));

                GeneralTransform transformToCanvasCoordinates = frameworkElement.TransformToAncestor(canvas);
                return transformToCanvasCoordinates.Transform(new Point(frameworkElement.ActualWidth / 2, frameworkElement.ActualHeight / 2));
            }
        }

        private static void OnStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            Connection connection = (Connection)sender;
            var start = args.NewValue as DragAwareListBoxItem;
            if (start != null)
            {
                start.ConnectionDragging += new Action(() =>
                {
                    connection.StartPoint = GetPointOnCenter(start);
                    connection.Path = connection.CalculateNewPathGeometry();
                });
            }
        }

        private static void OnEndPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            Connection connection = (Connection)sender;
            var end = args.NewValue as DragAwareListBoxItem;
            if (end != null)
            {
                end.ConnectionDragging += new Action(() =>
                {
                    connection.EndPoint = GetPointOnCenter(end);
                    connection.Path = connection.CalculateNewPathGeometry();
                });
            }
        }

        private static void OnLineColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            (sender as Connection).InvalidateVisual();
        }
    }
}
